﻿#if UNITY_2019_4_OR_NEWER
#define UNITY_3D
#endif

#if UNITY_3D
#if UNITY_EDITOR || COMMAND_PROFILER
#define PROFILING
#endif
using UnityEngine;
using UnityEngine.Networking;
#endif
using System.Collections;

namespace Devil
{
    #region defination
    public enum EExecutionState : byte
    {
        Standby,
        Executing,
        Complete,
    }

#if UNITY_3D
    public interface IDebugContext
    {
        Object GetContext();
    }
#endif

    /// <summary>
    /// 执行状态
    /// </summary>
    public struct Execution
    {
        public EExecutionState state;
        public float progress;
    }

    /// <summary>
    /// 执行进度反馈
    /// </summary>
    public interface IExecutableProgress
    {
        float Progress { get; }
    }

    /// <summary>
    /// 进度融合权重
    /// </summary>
    public interface IBlendableProgress
    {
        float BlendWeight { get; }
    }

    public interface ICommand
    {
        void Execute();
    }

    public interface IYieldInstruction
    {
        bool YieldContinue();
    }

    /// <summary>
    /// 携程命令
    /// </summary>
    public interface IYieldCommand
    {
        IEnumerator Execute(/*IThreadWorker worker*/);
    }


    /// <summary>
    /// 迭代命令
    /// </summary>
    public interface ICommandParallelFor<T>
    {
        void Execute(T data);
    }

    /// <summary>
    /// 中断命令信号
    /// </summary>
    public interface IBreakCommandSignal
    {
        void OnBreak();
    }

    /// <summary>
    /// 独立命令信号，实现该接口来设置是否允许命令是否允许关联中断
    /// </summary>
    public interface IIndependentCommandSignal
    {
        /// <summary>
        /// 是否允许关联中断
        /// </summary>
        /// <param name="isInterruptByDependent">是否是被依赖的任务打断</param>
        bool IsBreakable(bool isInterruptByDependent);
    }

    /// <summary>
    /// 循环执行指令
    /// </summary>
    public interface ILoopCommand
    {
        void Execute(float deltaTime);
    }

    public delegate IEnumerator YieldAction();
    public delegate IEnumerator YieldAction<T>(T arg);

    #endregion

    #region commands' implements

    public abstract class CmdContext
#if PROFILING
        : IDebugContext
#endif
    {

        public CmdContext(System.Delegate action)
        {
#if PROFILING
            try
            {
                desc = action.GetDelegateName();
                //string.Format("{0} [{1}: {2}]",
                //     action == null ? "Null" : action.Method.Name,
                //     action == null || action.Target == null ? "" : action.Target.GetType().Name,
                //     name);
                context = action == null ? null : action.Target as Object;
            }
            catch (System.Exception e)
            {
                Debug.LogException(e);
            }
#endif
        }

#if PROFILING
        string desc;
        Object context;
        public Object GetContext() { return context; }
        public override string ToString()
        {
            return desc;
        }
#endif

    }

    public class Cmd0 : CmdContext, ICommand
    {
        System.Action mAction;
        int mHashCode;
        public Cmd0(System.Action action) : base(action)
        {
            mHashCode = action.GetHashCode();
            this.mAction = action;
        }

        public void Execute()
        {
            mAction();
        }

        public override int GetHashCode()
        {
            return mHashCode;
        }

        public override bool Equals(object obj)
        {
            return obj is System.Action ? (System.Action)obj == mAction : obj == this;
        }
    }

    public class Cmd1<T> : CmdContext, ICommand
    {
        int mHashCode;
        T mArg;
        System.Action<T> mAction;

        public Cmd1(System.Action<T> action, T arg) : base(action)
        {
            this.mArg = arg;
            this.mAction = action; 
            mHashCode = mAction.GetHashCode();
            mHashCode = (mHashCode << 5) - mHashCode + (mArg == null ? 0 : mArg.GetHashCode());
        }

        public void Execute()
        {
            mAction(mArg);
        }

        public override int GetHashCode()
        {
            return mHashCode;
        }

        public override bool Equals(object obj)
        {
            return obj is System.Action<T> ? (System.Action<T>)obj == mAction : obj == this;
        }
    }

    public class CmdParallel<T> : CmdContext, ICommandParallelFor<T>
    {
        int mHashCode;
        System.Action<T> mAction;
        public CmdParallel(System.Action<T> action) : base(action)
        {
            mHashCode = action.GetHashCode();
            mAction = action;
        }

        public void Execute(T data)
        {
            mAction(data);
        }

        public override int GetHashCode()
        {
            return mHashCode;
        }

        public override bool Equals(object obj)
        {
            return obj is System.Action<T> ? (System.Action<T>)obj == mAction : obj == this;
        }
    }

    public class CmdYield : CmdContext, IYieldCommand
    {
        YieldAction mAction;
        int mHashCode;
        public CmdYield(YieldAction action) : base(action)
        {
            mHashCode = action.GetHashCode();
            mAction = action;
        }

        public IEnumerator Execute()
        {
            return mAction();
        }

        public override int GetHashCode()
        {
            return mHashCode;
        }

        public override bool Equals(object obj)
        {
            return obj is YieldAction ? (YieldAction)obj == mAction : obj == this;
        }

        //public static explicit operator CmdYield(YieldAction action)
        //{
        //    return new CmdYield(action);
        //}
    }

    public class CmdYield1<T> : CmdContext, IYieldCommand
    {
        T mArg;
        YieldAction<T> mAction;
        int mHashCode;
        public CmdYield1(YieldAction<T> action, T arg) : base(action)
        {
            mArg = arg;
            mAction = action;
            mHashCode = action.GetHashCode();
            mHashCode = (mHashCode << 5) - mHashCode + (mArg == null ? 0 : mArg.GetHashCode());
        }

        public IEnumerator Execute()
        {
            return mAction(mArg);
        }

        public override int GetHashCode()
        {
            return mHashCode;
        }

        public override bool Equals(object obj)
        {
            return obj is YieldAction<T> ? (YieldAction<T>)obj == mAction : obj == this;
        }
    }

    public class CmdInstruction : IYieldCommand
    {
        IYieldInstruction mInstruction;
        ValueDelegate<bool> mCondition;
#if UNITY_EDITOR
        string mDesc;
#endif
        public IYieldInstruction Instruction => mInstruction;

        public CmdInstruction(IYieldInstruction instruction)
        {
            mInstruction = instruction;
#if UNITY_EDITOR
            mDesc = $"yield<{instruction}>";
#endif
        }

        public CmdInstruction(ValueDelegate<bool> onCondition)
        {
            mCondition = onCondition;
#if UNITY_EDITOR
            mDesc = $"yield<{onCondition.GetDelegateName()}>";
#endif
        }

        IEnumerator IYieldCommand.Execute()
        {
            if (mInstruction != null)
                yield return mInstruction;
            if (mCondition != null)
            {
                while (mCondition())
                {
                    yield return null;
                }
            }
        }
#if UNITY_EDITOR
        public override string ToString()
        {
            return mDesc;
        }
#endif
    }

#endregion

    #region yield instructions

    public class WaitSeconds : IYieldInstruction
    {
        //float time;
        long delay;
        long _tick;

        public WaitSeconds(float secs)
        {
            delay = (long)(secs * 10000000d);
            _tick = System.DateTime.UtcNow.Ticks;
        }

        public void Reset(float delaySeconds = 0)
        {
            _tick = System.DateTime.UtcNow.Ticks;
            if (delay > 0)
                this.delay = (long)(delaySeconds * 10000000d);
        }

        public bool YieldContinue()
        {
            //time += worker.deltaTime;
            return System.DateTime.UtcNow.Ticks - _tick >= delay;
        }
    }

#if UNITY_3D
    public class UnityInstruction : IYieldInstruction
    {
        ValueDelegate<bool> mComplete;

        public UnityInstruction(AssetBundleCreateRequest req)
        {
            mComplete = () => req == null || req.isDone;
        }

        public UnityInstruction(AssetBundleRequest req)
        {
            mComplete = () => req == null || req.isDone;
        }

        public UnityInstruction(ResourceRequest req)
        {
            mComplete = () => req == null || req.isDone;
        }

        public UnityInstruction(AsyncOperation req)
        {
            mComplete = () => req == null || req.isDone;
        }

        public UnityInstruction(UnityWebRequest req, bool isUpload)
        {
            if (isUpload)
                mComplete = () => req == null || req.isDone;
            else
                mComplete = () => req == null || (req.isDone && (req.downloadHandler == null || req.downloadHandler.isDone));
        }

        public bool YieldContinue()
        {
            return mComplete == null || mComplete();
        }
    }
#endif

    #endregion

    #region loopers' implements

    public class CmdLooper : CmdContext, ILoopCommand
    {
        System.Action<float> mDelegate;
        internal CmdLooper(System.Action<float> dele) : base(dele)
        {
            mDelegate = dele;
        }

        public override bool Equals(object obj)
        {
            if (obj is System.Action<float>)
                return ((System.Action<float>)obj) == mDelegate;
            if (obj is CmdLooper)
                return ((CmdLooper)obj).mDelegate == mDelegate;
            return false;
        }

        public override int GetHashCode()
        {
            return mDelegate == null ? 0 : mDelegate.GetHashCode();
        }

        public void Execute(float deltaTime)
        {
            mDelegate(deltaTime);
        }

    }

    #endregion
}